//******************************************************************************
//                         新人研修用サンプルプログラム
//******************************************************************************
// ファイル名 : practice14.c
//******************************************************************************

//--------------------------------
// インクルードファイル
//--------------------------------
#include<stdio.h>

//--------------------------------
// マクロ
//--------------------------------
#define BUFFER_SIZE 255		// 一回に圧縮処理するデータ量
#define LINEHEADER 300		// ラインヘッダ追加作業一回分の上限データ量
//#define DEBUG_PRINT

/* デバッグ用 */
#ifdef DEBUG_PRINT
    #define DBG_PRINT(...) printf(__VA_ARGS__)
#else
    #define DBG_PRINT(...)
#endif

//--------------------------------
// グローバル変数
//--------------------------------
unsigned char buffer[BUFFER_SIZE];		// 圧縮処理一回分のデータを入れる配列
unsigned char lineheader[LINEHEADER];	// ラインヘッダ付加作業用配列

//--------------------------------
// プロトタイプ宣言
//--------------------------------
static int encode(unsigned char* img, int max, FILE*);
static int header(FILE* fp, FILE* fp_t, FILE* fp_255);
static int get_len(unsigned char* img, int x, int max, int lim, unsigned char* run, unsigned char* code);
static int add_top(FILE* fp_top);
static int decode(FILE*, FILE*);

//***************************************************************************
//機能名称 : メイン関数
//関数名：int main()
//引数　：なし
//戻り値：常に0
//機能　：元データファイルと圧縮ファイル作成用ファイルを開き元データを255バイトずつ
//　　　　関数へ渡す。
//　　　：圧縮ファイルを解凍して、新しく同一データのファイルを作成する。
//***************************************************************************
int main(void){

	FILE *fp_i;			// 元データオープン用ファイルポインタ
	FILE *fp_t;			// 解凍後ファイルオープン用ファイルポインタ
	FILE *fp_255;		// 圧縮ファイルオープンファイルポインタ
	unsigned short i;	// 255バイト分データ読込配列制御用変数
	unsigned int c;		// 読取データ格納用変数

	DBG_PRINT("main_start\n");

	/* 入力ファイルを開く*/
	fp_i=fopen("input.bmp","rb");
   	if(fp_i == NULL){
			fprintf(stderr,"ファイルが開けません。");
		return -1;
    }
	/* 出力ラインヘッダ下位用：255バイト中の繰り返し数記録ファイルを開く */
	fp_255=fopen("255.txt","wb+");
    	if(fp_255 == NULL){
			fprintf(stderr,"ファイルが開けません。");
		return -1;
	}

	/* ファイルの内容をbuffer[]に読取り */
	/* 1個目の文字を読む */
	i = 0;
    c = fgetc(fp_i);
    /* EOFまできたか */
    while(c != EOF){
		// 255バイト分だけ読む
    	while(i < BUFFER_SIZE){
			buffer[i] = c;
			i++;
			if(i != BUFFER_SIZE){
				/* 2個目の文字に合わせる */
				c = fgetc(fp_i);
				/* バッファサイズにあまりがあってもEOFなら読取り終了 */
				if(c == EOF){
					break;
				}
			}
		}
		/* 圧縮処理 */
    	encode(buffer, i, fp_255);
    	/* 2回目以降の処理の準備 */
		i = 0;
    	c = fgetc(fp_i);
    }
	/* 先頭ヘッダ付加処理 */
    add_top(fp_255);
     
    /* 元データファイルを閉じる */
    fclose(fp_i);
    /* 圧縮ファイルを閉じる */
	fclose(fp_255);

	/* 圧縮ファイルを開く */
    fp_255 = fopen("comp.txt","rb");
    if(fp_255 == NULL){
		fprintf(stderr,"ファイルが開けません。");
		return -1;
    }
    /* 解凍後ファイルを開く */
    fp_t = fopen("fin.bmp","wb");
    if(fp_t == NULL){
		fprintf(stderr,"ファイルが開けません。");
		return -1;
    }

	/* 解凍処理 */
	decode(fp_255, fp_t);
	/* 圧縮ファイル */
	fclose(fp_255);
	/* 解凍後ファイル */ 
	fclose(fp_t);

	return 0;
}

//***************************************************************************
// 機能名称：長さ算出関数
// 関数名：static int get_len()
// 引数　：unsigned char* img, int x, int max, int lim, int* run, int* code
// 戻り値：px 次に参照するデータの添字
// 機能　：同じデータが並ぶ長さを求める
//***************************************************************************
static int get_len(unsigned char* img, int x, int max, int lim, unsigned char* run, unsigned char* code){
	/* img[]配列のx番から最大lim個の文字を読み、連続する文字数をrunに、文字をcodeに返す */
	unsigned short px; // バッファの添字

	px = x;
    *run = 1;
    *code = img[px];
    px++;

	/* 同じ文字であれば繰り返して読む */
	/* pxは次の文字の添字,run は連続文字数 */
	while(px < max && *code == img[px] && *run < lim){
		px++;
		(*run)++;
    }
	return px;
}

//***************************************************************************
// 機能名称：データ圧縮関数
// 関数名：static int encode()
// 引数　：FILE *fp, unsigned char* img, int max, FILE* fp_255
// 戻り値：常に0
// 機能　：データを圧縮する。
// 　　　：2回以上の連続データはランレングス符号化する。
// 　　　：繰り返さないデータはそのままにする。
//***************************************************************************
static int encode(unsigned char* img, int max, FILE* fp_255){

	unsigned char run = 0;			// ファイル書出し回数格納用
	unsigned char code = 0;			// ファイル書出しデータ格納用
    int x;							// 文字連続数格納用
    unsigned short lh = 0;			// ラインヘッダ情報
    unsigned short s_linecnt = 0;	// 255バイト中非圧縮対象データ連続回数格納用
    unsigned short c_linecnt = 0;	// 255バイト中データ圧縮回数格納用
    char c_flg = 0;					// 圧縮データ書出しフラグ
    char s_flg = 0;					// 非圧縮データ書出しフラグ
    
	x = 0;
	unsigned short buf_cnt = 0;		// データ書出し処理制御用

	while(x < max){
   		/* ファイルから、連続文字を読む */
   		x = get_len(img, x, max, 0x7fff, &run, &code);

   		/* 1文字の場合 */
   		if(run < 2){
			/* 圧縮データ書出し処理 */
			if(c_flg != 0){
				c_linecnt = c_linecnt | 0x8000;
				fwrite(&c_linecnt, sizeof(short), 1, fp_255);
				lineheader[lh] = '\0';
				buf_cnt = lh;
				for(lh = 0; lh < buf_cnt; lh++){
					fwrite(&lineheader[lh], sizeof(char), 1, fp_255);
				}
				lh = 0;

				c_linecnt = 0;
				c_flg = 0;
        	}
			/* 非圧縮データ格納処理 */
			if(code < 0x80){	
        		lineheader[lh] = code;
				lh++;
        	}
        	else{
				lineheader[lh] = code;
				lh++;
            }
            s_linecnt++;
			s_flg = 1;
		}
        /* 圧縮(繰り返しあり)の場合 */
        else{
			/* 非圧縮データ書出し処理 */
			if(s_flg != 0){
				fwrite(&s_linecnt, sizeof(short),1, fp_255);
				lineheader[lh] = '\0';
				buf_cnt = lh;
				for(lh = 0; lh < buf_cnt; lh++){
					fwrite(&lineheader[lh], sizeof(char), 1, fp_255);
				}
				lh = 0;
				s_linecnt = 0;
				s_flg = 0;
			}
			/* 圧縮データ格納処理 */
	    	lineheader[lh] = run;
			lh++;
           	lineheader[lh] = code;
			lh++;
           	c_linecnt++;
	    	c_flg = 1;
        }
    }
	/* 圧縮データ書出し処理 */
	if(c_flg != 0){
		c_linecnt = c_linecnt | 0x8000;
		fwrite(&c_linecnt, sizeof(short),1, fp_255);
		lineheader[lh] = '\0';
		buf_cnt = lh;
		for(lh = 0; lh < buf_cnt; lh++){
			fwrite(&lineheader[lh], sizeof(unsigned char), 1, fp_255);
		}
		lh = 0;
	}
	/* 非圧縮データ書出し処理 */
	else if(s_flg != 0){

		fwrite(&s_linecnt, sizeof(short),1, fp_255);
		lineheader[lh] = '\0';
		buf_cnt = lh;
		for(lh = 0; lh < buf_cnt; lh++){
			fwrite(&lineheader[lh], sizeof(unsigned char), 1, fp_255);
		}
		lh = 0;
	}
	return 0;
}

//***************************************************************************
// 機能名称：先頭ヘッダ付加関数
// 関数名：static int add_top()
// 引数　：FILE *fp_top
// 戻り値：常に0
// 機能　：圧縮ファイルのサイズを調べて、ファイルの先頭に追加する。
//***************************************************************************
static int add_top(FILE* fp_top){

	FILE* fp_comp;					// 先頭ヘッダ付加後圧縮ファイルオープン用
	fpos_t top_size = 0;			// 先頭ヘッダ付加前圧縮ファイルサイズ格納用

	/* 圧縮ファイルデータ数取得処理 */
	fseek(fp_top, 0, SEEK_SET);
	fseek(fp_top, 0, SEEK_END);
	fgetpos(fp_top, &top_size);

	fp_comp = fopen("comp.txt","wb");
    	if(fp_comp == NULL){
		fprintf(stderr,"ファイルが開けません。");
		return -1;
	}
	/* 先頭ヘッダ追加処理 */
	fseek(fp_comp, 0 ,SEEK_SET);
	fwrite(&top_size, sizeof(unsigned int), 1, fp_comp);

	fseek(fp_top, 0, SEEK_SET);
	/* 圧縮データコピー処理 */
	while(fgetc(fp_top) != EOF){
		fseek(fp_top, -1, SEEK_CUR);
		fputc(fgetc(fp_top), fp_comp);
	}
	fclose(fp_comp);
	return 0;
}

//***************************************************************************
// 機能名称：データ解凍関数
// 関数名：static int decode()
// 引数　：FILE *fp_255, FILE* fp_t
// 戻り値：常に0
// 機能　：圧縮データを解凍する。
// 　　　：解凍データを新ファイルに出力する。
//***************************************************************************
static int decode(FILE* fp_255, FILE* fp_t){
	
	unsigned char loop_end = 0;		// 255バイト中圧縮回数格納用
	unsigned char code;				// 読取データ(データ)格納用
    unsigned char run;				// 読取データ(回数)格納用
    unsigned short lhd;				// 圧縮データ書出しループ制御用
	unsigned short show = 0;		// 圧縮ファイル読み取りデータ格納用
	short loop = 0;					// ラインヘッダ情報書出し制御用
	fpos_t fpos;					// 圧縮ファイル読取位置格納用

	fseek(fp_255, 4 , SEEK_SET);
	fgetpos(fp_255, &fpos);
		
    /* ラインヘッダ下位用：書き出したバイナリデータを読み込んでみる */
	while(fgetc(fp_255) != EOF){
		
		fsetpos(fp_255, &fpos);
		
		/* 書き出したデータ読込 */
		fread(&show, sizeof(short), 1, fp_255);
		/* 圧縮データ解凍処理 */
		if(show & 0x8000){
			loop_end = show & 0x7fff;
			for(lhd = 0; lhd < loop_end; lhd++){
				fread(&show, sizeof(short), 1, fp_255);
				code = (show >> 8) & 0xff;
				run = show & 0x00ff;
				
				for(loop = 0; loop < run; loop++){
					fwrite(&code, sizeof(char), 1, fp_t);
				}
			}
		}
		/* 非圧縮データ解凍処理 */
		else{
			/* 書出したデータ読込 */
			run = show & 0x7fff;
			for(loop = 0; loop < run; loop++){
				fread(&code, sizeof(char), 1, fp_255);
				fwrite(&code, sizeof(char), 1, fp_t);
			}		
		}
		fgetpos(fp_255, &fpos);
	}
	return 0;
}
